/********************************************************************
	created:	2011/9/5
	filename: 	GameStage.cpp
	author:		Mwolf
	
	purpose:	
*********************************************************************/
#include "GameStage.h"
#include "Renderer/TextureManager.h"
#include "GWLog.h"
#include "AreaTrigger.h"

#include <cstring>
#include <string>
#include <vector>
#include <algorithm>

#if defined GW_PLATFORM_PSP
#include <lua.hpp>
#else
#include "Lua/lua.hpp"
#endif

#include "GameMain.h"

#define TIME_WORLD_SWAP_ANIM 1000

typedef struct TileUsageTable
{
	TileUsageEnum	usage;
	char			name[256];
} TILE_USAGE_TABLE;

TILE_USAGE_TABLE tile_usage_table[] =
{
	{ TILE_USAGE_SOLID,			"SOLID"		},
	{ TILE_USAGE_LADDER,		"LADDER"	},
	{ TILE_USAGE_TRIGGER_HURT,	"HURT"		},
	{ TILE_USAGE_END,			""			},
};

TileUsageEnum StringToTileUsage( const char* type_name )
{
	for ( int i = 0; tile_usage_table[i].usage != TILE_USAGE_END ; i++ )
	{
		if ( strcmp(type_name, tile_usage_table[i].name) == 0 )
		{
			return (TileUsageEnum)i;
		}
	}

	return TILE_USAGE_VOID;
}

const char* TileUsageToString( TileUsageEnum usage )
{
	return tile_usage_table[usage].name;
}

typedef struct StageGeomListType
{
	STAGE_GEOM*				StageGeomHead;
	STAGE_GEOM*				StageGeomTail;
	int						geom_count;
} STAGE_GEOM_LIST;

static STAGE_GEOM_LIST StageGeomList = { NULL, NULL, 0 };

// Stage geometry methods
STAGE_GEOM* CreateStageGeom()
{
	STAGE_GEOM* geom = new STAGE_GEOM();
	geom->next = NULL;

	if (StageGeomList.StageGeomTail == NULL)
	{
		// Build new linked list
		StageGeomList.StageGeomHead = geom;
		StageGeomList.StageGeomTail = geom;
	}
	else
	{
		// Add new geometry to the tail
		StageGeomList.StageGeomTail->next = geom;
		StageGeomList.StageGeomTail = geom;
	}

	return geom;
}

STAGE_GEOM* GetFirstStageGeom()
{
	return StageGeomList.StageGeomHead;
}

STAGE_GEOM* GetNextStageGeom(STAGE_GEOM* geom)
{
	return geom->next;
}

int GetStageGeomCount()
{
	return StageGeomList.geom_count;
}

void DebugRenderStageGeom( STAGE_GEOM* geom )
{
	if (geom->tile_type_id != -1)
	{
		RenderSystem::DrawWireframeRect(Vector2(geom->post_proj_bound.xMin, geom->post_proj_bound.yMin),
										Vector2(geom->post_proj_bound.xMax, geom->post_proj_bound.yMax),
										GWColor(1.0f, 0.949f, 0.0f), -0.01f);
	}
}

GameStage::GameStage()
: m_TileTypeIndex(0)
{
	m_WorldSwapTime = KleinGame()->GetSysTickCount();
}

GameStage::~GameStage()
{
	Reset();
}

bool GameStage::LoadFromFile( const char* filename )
{
	bool result = false;
	lua_State* pLuaState = lua_open();
	//luaL_openlibs(pLuaState);
	int err_code = luaL_dofile(pLuaState, filename);

	if (!err_code)
	{
		lua_getglobal(pLuaState, "Stage");

		if (lua_istable(pLuaState, -1))
		{
			//int layers = stage["Layers"].GetInteger();

			// Load tile types
			GWLog_Print("Load tile types.\n");
			ScriptLoadTileTypes(pLuaState);

			// Load geometries for the stage
			GWLog_Print("Load geometries.\n");
			ScriptLoadGeometries(pLuaState);

			// Load area triggers
			GWLog_Print("Load area triggers.\n");
			ScriptLoadTriggers(pLuaState);

			result = true;
		}
	}

	lua_close(pLuaState);

	return result;
}

#if !defined KLEIN_NO_STAGE_SAVE
bool GameStage::SaveToFile( const char* filename )
{
	FILE* fp = fopen( filename, "w" );

	if ( !fp )
		return false;

	fprintf( fp, "Stage =\n" );
	fprintf( fp, "{\n" );

	ScriptSaveGeometries( fp );
	ScriptSaveTileTypes( fp );
	ScriptSaveTriggers( fp );

	fprintf( fp, "}\n" );

	fclose( fp );

	return true;
}
#endif

void GameStage::RenderStage()
{
	STAGE_GEOM* geom;

	float depth = 0.0f;

	// Render current world
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		RenderStageGeom(geom, 0);
		//DebugRenderStageGeom(geom);

#if defined T3_DEBUG_DRAW_COLLISION
		if (geom->draw_collision)
			DebugRenderStageGeom(geom);
#endif	// #if defined T3_DEBUG_DRAW_COLLISION
	}

}

void GameStage::DebugRenderStage()
{
	STAGE_GEOM* geom;

	// Render current world
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		DebugRenderStageGeom(geom);
	}
}

void GameStage::Reset()
{
	STAGE_GEOM* geom;
	for (geom = GetFirstStageGeom(); geom != NULL; )
	{
		STAGE_GEOM* old_geom = geom;
		geom = GetNextStageGeom(geom);
		delete old_geom;
	}

	StageGeomList.StageGeomHead = NULL;
	StageGeomList.StageGeomTail = NULL;
	StageGeomList.geom_count = 0;

	m_TileTypeIndex = 0;
	m_TileName2Id.clear();
	m_TileId2TypeInfo.clear();
}

void GameStage::Update()
{
#if defined T3_DEBUG_DRAW_COLLISION
	STAGE_GEOM* geom;

	// Reset collision debug drawing
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		geom->draw_collision = false;
	}
#endif	// #if defined T3_DEBUG_DRAW_COLLISION
}

static Vector3 Transform( const Vector3& vec, const Matrix4& mat )
{
	float x, y, z, w;

	x = vec.x * mat.m11 + vec.y * mat.m21 + vec.z * mat.m31 + 1.f * mat.m41;
	y = vec.x * mat.m12 + vec.y * mat.m22 + vec.z * mat.m32 + 1.f * mat.m42;
	z = vec.x * mat.m13 + vec.y * mat.m23 + vec.z * mat.m33 + 1.f * mat.m43;
	w = vec.x * mat.m14 + vec.y * mat.m24 + vec.z * mat.m34 + 1.f * mat.m44;

	return Vector3(x, y, z);
}

static BoundBox GetPostProjectionBound(const BoundBox& box, float depth)
{
#if 0
	BoundBox output = box;
	T3Camera* camera = KleinGame()->GetCamera();
	float p = T3Camera_GetAnimationProgress( camera );
	T3CameraTilt tilt = camera->tilt;

	switch (tilt)
	{
	case T3_CAMERA_TILT_LEFT:
		output.xMin -= p;
		output.Translate(Vector2(-p, 0.f) * depth);
		break;
	case T3_CAMERA_TILT_RIGHT:
		output.xMax += p;
		output.Translate(Vector2(p, 0.f) * depth);
		break;
	case T3_CAMERA_TILT_UP:
		output.yMax += p;
		output.Translate(Vector2(0.f, p) * depth);
		break;
	case T3_CAMERA_TILT_DOWN:
		output.yMin -= p;
		output.Translate(Vector2(0.f, -p) * depth);
		break;
	default:
		break;
	}
#else

	//Vector3 vMin = Vector3( box.vMin(), depth ),
	//		vMax = Vector3( box.vMax(), depth );

	//vMin = Transform( vMin, KleinGame()->GetSceneTransform() );
	//vMax = Transform( vMax, KleinGame()->GetSceneTransform() );

	//if (vMin.x > vMax.x) std::swap(vMin.x, vMax.x);
	//if (vMin.y > vMax.y) std::swap(vMin.y, vMax.y);
	//if (vMin.z > vMax.z) std::swap(vMin.z, vMax.z);

	Vector3 v[8] =
	{
		Vector3( box.xMin, box.yMin, depth ),
		Vector3( box.xMax, box.yMin, depth ),
		Vector3( box.xMin, box.yMax, depth ),
		Vector3( box.xMax, box.yMax, depth ),

		Vector3( box.xMin, box.yMin, depth + 1.f ),
		Vector3( box.xMax, box.yMin, depth + 1.f ),
		Vector3( box.xMin, box.yMax, depth + 1.f ),
		Vector3( box.xMax, box.yMax, depth + 1.f ),
	};

	Vector3 vMin, vMax;

	for (int i=0; i<8; i++)
	{
		Vector3 point = Transform( v[i], KleinGame()->GetSceneTransform() );
		if (i == 0)
		{
			vMin = point;
			vMax = point;
		}
		else
		{
			if (point.x < vMin.x) vMin.x = point.x;
			if (point.y < vMin.y) vMin.y = point.y;
			if (point.z < vMin.z) vMin.z = point.z;

			if (point.x > vMax.x) vMax.x = point.x;
			if (point.y > vMax.y) vMax.y = point.y;
			if (point.z > vMax.z) vMax.z = point.z;
		}
	}

	BoundBox output( vMin.x, vMin.y, vMax.x, vMax.y );
#endif
	return output;
}

static bool TiltLeftSort(STAGE_GEOM* lhs, STAGE_GEOM* rhs)
{
	return lhs->post_proj_bound.xMin < rhs->post_proj_bound.xMin;
}

static bool TiltRightSort(STAGE_GEOM* lhs, STAGE_GEOM* rhs)
{
	return lhs->post_proj_bound.xMin > rhs->post_proj_bound.xMin;
}

void GameStage::TestCollision( Actor* actor )
{
	bool result = false;
	Vector2 rel = actor->MoveController() + actor->Velocity();

	std::vector<STAGE_GEOM*> col_group;

	STAGE_GEOM* geom;

	// Collect collision objects
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		// Update post-projection bound box
		geom->post_proj_bound = GetPostProjectionBound( geom->bound, geom->depth );

		bool collide = false;
		if ( GetTileUsageById(geom->tile_type_id) != TILE_USAGE_SOLID )
			continue;

		if ( actor->TestCollision( geom->post_proj_bound, rel ) )
			collide = true;

		if (collide)
			col_group.push_back(geom);

#if defined T3_DEBUG_DRAW_COLLISION
		geom->draw_collision |= collide;
#endif	// #if defined T3_DEBUG_DRAW_COLLISION
	}

	if (!col_group.empty())
	{
		switch (KleinGame()->GetCameraTilt())
		{
		case T3_CAMERA_TILT_LEFT:
			std::sort(col_group.begin(), col_group.end(), TiltLeftSort);
			break;
		case T3_CAMERA_TILT_RIGHT:
			std::sort(col_group.begin(), col_group.end(), TiltRightSort);
			break;
		}

		Vector2 rel_x, rel_y;
		rel_x = rel;
		rel_y = rel;

		rel_x.y = 0.0f;
		rel_y.x = 0.0f;

		std::vector<STAGE_GEOM*>::iterator iter;
		// Test collision move in x dir and y dir separately
		for ( iter = col_group.begin(); iter != col_group.end(); iter++ )
		{
			result |= actor->DoCollisionMove( (*iter), rel_y, &rel_y );
		}

		rel = rel_x + rel_y;

		for ( iter = col_group.begin(); iter != col_group.end(); iter++ )
		{
			result |= actor->DoCollisionMove( (*iter), rel, &rel );
		}
	}

	actor->Translate(rel);

	// Update morph for stage geom
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		geom->post_proj_bound_last_frame = geom->post_proj_bound;
	}
}

STAGE_GEOM* GameStage::GetTileAtPoint( const Vector2& point )
{
	STAGE_GEOM* geom;
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		if ( geom->post_proj_bound.IsPointInsideBox( point ) )
			return geom;
	}

	return NULL;
}

STAGE_GEOM* GameStage::GetTileAtPoint( const Vector2& point, float depth )
{
	STAGE_GEOM* geom;
	for (geom = GetFirstStageGeom(); geom != NULL; geom = GetNextStageGeom(geom))
	{
		if ( geom->bound.IsPointInsideBox( point ) && geom->depth == depth )
			return geom;
	}

	return NULL;
}

TileUsageEnum GameStage::GetStageGeomUsage( STAGE_GEOM* geom )
{
	return GetTileUsageById(geom->tile_type_id);
}


const char* GameStage::GetTileNameById( int tile_id ) const
{
	std::map<std::string, int>::const_iterator iter;
	for (iter=m_TileName2Id.begin(); iter!=m_TileName2Id.end(); iter++)
	{
		if (iter->second == tile_id)
			return iter->first.c_str();
	}

	return NULL;
}

int GameStage::GetTileIdByName( const char* tile_name ) const
{
	std::map<std::string, int>::const_iterator iter;
	if ( (iter = m_TileName2Id.find(tile_name)) != m_TileName2Id.end() )
		return iter->second;

	return -1;
}

STAGE_GEOM* GameStage::AddStageGeom( int layer_id, const BoundBox& bound, const char* tile_type_name )
{
	STAGE_GEOM* geom = CreateStageGeom();

	geom->depth = (float)layer_id;

	// Find texture id by name
	int geom_tile_type = -1;
	TileUsageEnum t = TILE_USAGE_VOID;
	if (m_TileName2Id.find(tile_type_name)!=m_TileName2Id.end())
	{
		geom_tile_type = m_TileName2Id[tile_type_name];
	}
	geom->tile_type_id = geom_tile_type;

	geom->bound = bound;
	geom->post_proj_bound = bound;
	geom->post_proj_bound_last_frame = bound;
	
	return geom;
}

void GameStage::AnimSwapWorlds()
{
	m_WorldSwapTime = KleinGame()->GetSysTickCount() + TIME_WORLD_SWAP_ANIM;
}

void GameStage::ScriptLoadTileTypes( lua_State* state )
{
	lua_pushstring(state, "TileTypes");
	lua_gettable(state, -2);

	// ["TileTypes"]
	if (lua_istable(state, -1))
	{
		//int tiletype_count = lua_objlen(state, -1);
		GWLog_Print("Tile type count: %d\n", lua_objlen(state, -1));

		lua_pushnil(state);

		// Read elements
		while (lua_next(state, -2) != 0)
		{
			if (lua_istable(state, -1))
			{
				lua_pushnil(state);

				const char* lua_str[3] = { NULL };
				int s = 0;

				// Strings
				while (lua_next(state, -2) != 0)
				{
					int t = lua_type(state, -1);
					if (lua_type(state, -1) == LUA_TSTRING)
						lua_str[s] = lua_tostring(state, -1);

					GWLog_Print("Tile type string[%d]: %s\n", s, lua_str[s]);

					s++;
					lua_pop(state, 1);
				}

				// Load tile type info into common container
				std::string tile_name = lua_str[0];

				TILE_TYPE_INFO tile_info;
				tile_info.usage = StringToTileUsage(lua_str[1]);
				const char* tex_name = lua_str[2];

				int texID = -1;
				if ( (texID=TextureManager::Instance().GetTextureID(tex_name)) == -1)
				{
					if (TextureManager::Instance().LoadTextureFromFile(tex_name))
						texID = TextureManager::Instance().GetTextureID(tex_name);
				}

				tile_info.tex_id = texID;

				// Add tile type to stage containers
				m_TileName2Id[tile_name] = m_TileTypeIndex;
				m_TileId2TypeInfo[m_TileTypeIndex] = tile_info;
				m_TileTypeIndex++;
			}

			lua_pop(state, 1);
		}
	}

	lua_pop(state, 1);
}

void GameStage::ScriptLoadGeometries( lua_State* state )
{
	lua_pushstring(state, "Geometries");
	lua_gettable(state, -2);

	// ["Geometries"]
	if (lua_istable(state, -1))
	{
		StageGeomList.geom_count = lua_objlen(state, -1);
		GWLog_Print("Total stage geom count: %d\n", StageGeomList.geom_count);

		lua_pushnil(state);

		// Elements
		while (lua_next(state, -2) != 0)
		{
			if (lua_istable(state, -1))
			{
				lua_pushnil(state);

				float lua_num[5];
				const char* lua_str;
				int n = 0;

				// Strings
				while (lua_next(state, -2) != 0)
				{
					if (lua_isnumber(state, -1))
					{
						lua_num[n] = (float)lua_tonumber(state, -1);
						GWLog_Print("Stage geom num[%d]: %f\n", n, lua_tonumber(state, -1));
						GWLog_Print("Stage geom num[%d]: %f\n", n, lua_num[n]);
						n++;
					}
					else if (lua_isstring(state, -1))
					{
						lua_str = lua_tostring(state, -1);
						GWLog_Print("Stage geom string[%d]: %s\n", s, lua_str[s]);
					}

					lua_pop(state, 1);
				}

				BoundBox box;

				int layer = (int)lua_num[0];
				box.xMin = lua_num[1];
				box.xMax = lua_num[2];
				box.yMin = lua_num[3];
				box.yMax = lua_num[4];

				GWLog_Print("\n");

				AddStageGeom(layer, box, lua_str);
			}

			lua_pop(state, 1);
		}
	}

	lua_pop(state, 1);
}

void GameStage::ScriptLoadTriggers( lua_State* state )
{
	lua_pushstring(state, "AreaTriggers");
	lua_gettable(state, -2);

	// ["AreaTriggers"]
	if (lua_istable(state, -1))
	{
		//StageGeomList.geom_count = lua_objlen(state, -1);
		GWLog_Print("Total area trigger count: %d\n", lua_objlen(state, -1));
		lua_pushnil(state);

		// Elements
		while (lua_next(state, -2) != 0)
		{
			if (lua_istable(state, -1))
			{
				lua_pushnil(state);

				float lua_num[5];
				const char* lua_str[1];
				int n = 0, s = 0;

				// Strings
				while (lua_next(state, -2) != 0)
				{
					int t = lua_type(state, -1);
					if (t == LUA_TSTRING)
					{
						lua_str[s] = lua_tostring(state, -1);
						GWLog_Print("Area trigger string[%d]: %s\n", s, lua_str[s]);
						s++;
					}
					else if (t == LUA_TNUMBER)
					{
						lua_num[n] = (float)lua_tonumber(state, -1);
						GWLog_Print("Area trigger num[%d]: %f\n", n, lua_num[n]);
						n++;
					}

					lua_pop(state, 1);
				}

				AREA_TRIGGER* trigger = new AREA_TRIGGER;
				BoundBox box;

				box.xMin = lua_num[0];
				box.xMax = lua_num[1];
				box.yMin = lua_num[2];
				box.yMax = lua_num[3];
				trigger->bound = box;

				const char* callback_name = lua_str[0];
				trigger->callback = StringToTriggerFunc(callback_name);

				AddAreaTriggerToGame(trigger);
			}

			lua_pop(state, 1);
		}
	}

	lua_pop(state, 1);
}

#if !defined KLEIN_NO_STAGE_SAVE
void GameStage::ScriptSaveGeometries( FILE* fp )
{
	fprintf( fp, "\tGeometries =\n");
	fprintf( fp, "\t{\n");

	STAGE_GEOM* geom;
	for (geom = GetFirstStageGeom(); geom!=NULL; geom = GetNextStageGeom(geom))
	{
		// Don't output if tile is null in all worlds
		if (geom->tile_type_id == -1)
			continue;

		fprintf( fp, "\t\t{ %d, %d, %d, %d, %d",
				 (int)geom->depth,
				 (int)geom->bound.xMin,
				 (int)geom->bound.xMax,
				 (int)geom->bound.yMin,
				 (int)geom->bound.yMax);

		std::map<std::string, int>::iterator iter;

		for (iter=m_TileName2Id.begin(); iter!=m_TileName2Id.end(); iter++)
		{
			if (iter->second == geom->tile_type_id)
			{
				fprintf( fp, ", \"%s\"", iter->first.c_str() );
				break;
			}
		}

		if (iter==m_TileName2Id.end())
		{
			fprintf( fp, ", \"\"" );
		}
		fprintf( fp, " },\n" );
	}

	fprintf( fp, "\t},\n" );
}		

void GameStage::ScriptSaveTileTypes( FILE* fp )
{
	fprintf( fp, "\tTileTypes =\n" );
	fprintf( fp, "\t{\n");

	std::map<std::string, int>::iterator iter;
	for (iter=m_TileName2Id.begin(); iter!=m_TileName2Id.end(); iter++)
	{
		int tile_id = iter->second;
		int tex_id = m_TileId2TypeInfo[tile_id].tex_id;

		fprintf( fp, "\t\t{ \"%s\", \"%s\", \"%s\" },\n",
				 iter->first.c_str(),
				 TileUsageToString(m_TileId2TypeInfo[tile_id].usage),
				 TextureManager::Instance().GetTextureName(tex_id) );
	}

	fprintf( fp, "\t},\n" );
}

void GameStage::ScriptSaveTriggers( FILE* fp )
{
	fprintf( fp, "\tAreaTriggers =\n" );
	fprintf( fp, "\t{\n");

	AREA_TRIGGER* trigger = GetFirstAreaTrigger();
	while (trigger)
	{
		fprintf( fp, "\t\t{ %d, %d, %d, %d, \"%s\" },\n",
				 (int)trigger->bound.xMin,
				 (int)trigger->bound.xMax,
				 (int)trigger->bound.yMin,
				 (int)trigger->bound.yMax,
				 TriggerFuncToString(trigger->callback)	);

		trigger = GetNextAreaTrigger(trigger);
	}

	fprintf( fp, "\t},\n" );
}

#endif

void GameStage::RenderStageGeom( STAGE_GEOM* geom, int world_id, float depth/*=0.0f*/ )
{
	int tile_id = geom->tile_type_id;

	// Don't render tile with no type
	if (tile_id != -1)
	{
		const TEXTURE_INFO* tex = TextureManager::Instance().GetTexture(m_TileId2TypeInfo[tile_id].tex_id);

		float fScaleAbs = 0.0f;	// Add scale amount to make geom bigger

		RenderSystem::DrawCube(  Vector2(geom->bound.xMin - fScaleAbs, geom->bound.yMin - fScaleAbs),
								 Vector2(geom->bound.xMax + fScaleAbs, geom->bound.yMax + fScaleAbs),
								 tex, geom->depth + depth);
	}
}

TileUsageEnum GameStage::GetTileUsageById( int id )
{
	if ( m_TileId2TypeInfo.find(id) != m_TileId2TypeInfo.end() )
	{
		return m_TileId2TypeInfo[id].usage;
	}

	return TILE_USAGE_VOID;
}
